package com.gaxy.subject.infra.basic.service.impl;


import com.gaxy.subject.common.entity.PageResult;
import com.gaxy.subject.common.enums.SubjectInfoTypeEnum;
import com.gaxy.subject.infra.basic.entity.ESSubjectFields;
import com.gaxy.subject.infra.basic.entity.SubjectInfoES;
import com.gaxy.subject.infra.basic.es.ESIndexInfo;
import com.gaxy.subject.infra.basic.es.ESRestClient;
import com.gaxy.subject.infra.basic.es.ESSearchRequest;
import com.gaxy.subject.infra.basic.es.ESSourceData;
import com.gaxy.subject.infra.basic.service.SubjectESService;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;


/**
 * ESService实现类
 *
 * @author Gaxy
 * @create 2024/06/25/上午 11:43
 */
@Service
@Slf4j
public class SubjectESServiceImpl implements SubjectESService {
    @Override
    public boolean insert(SubjectInfoES subjectInfoES) {
        ESSourceData esSourceData = new ESSourceData();
        Map<String, Object> data = convert2ESSourceData(subjectInfoES);
        esSourceData.setDocId(subjectInfoES.getDocId().toString());
        esSourceData.setData(data);
        return ESRestClient.insertDoc(getESIndexInfo(), esSourceData);
    }

    /**
     * 根据SubjectInfoES对象生成对应的Map<String, Object> data
     * @param subjectInfoES
     * @return
     */
    private Map<String, Object> convert2ESSourceData(SubjectInfoES subjectInfoES) {
        Map<String, Object> data = new HashMap<>();
        data.put(ESSubjectFields.SUBJECT_ID, subjectInfoES.getSubjectId());
        data.put(ESSubjectFields.DOC_ID, subjectInfoES.getDocId());
        data.put(ESSubjectFields.SUBJECT_NAME, subjectInfoES.getSubjectName());
        data.put(ESSubjectFields.SUBJECT_ANSWER, subjectInfoES.getSubjectAnswer());
        data.put(ESSubjectFields.SUBJECT_TYPE, subjectInfoES.getSubjectType());
        data.put(ESSubjectFields.CREATE_USER, subjectInfoES.getCreateUser());
        data.put(ESSubjectFields.CREATE_TIME, subjectInfoES.getCreateTime());
        return data;
    }

    @Override
    public PageResult<SubjectInfoES> querySubjectList(SubjectInfoES req) {
        /*
            执行搜索请求，从ES中拿到查询的响应
         */
        PageResult<SubjectInfoES> pageResult = new PageResult<>();
        ESSearchRequest esSearchRequest = createSearchListQuery(req);
        SearchResponse searchResponse = ESRestClient.searchWithTermQuery(getESIndexInfo(), esSearchRequest);

        /*
            处理命中的文档，转换至subjectInfoESList
         */
        List<SubjectInfoES> subjectInfoESList = new LinkedList<>();
        SearchHits searchHits = searchResponse.getHits();
        // 没有命中任何文档
        if (searchHits == null || searchHits.getHits() == null){
            pageResult.setPageNo(req.getPageNo());
            pageResult.setPageSize(req.getPageSize());
            pageResult.setRecords(subjectInfoESList);
            pageResult.setTotal(0);
            return pageResult;
        }
        // 转换命中的文档
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            SubjectInfoES subjectInfoES = convertResult(hit);
            if (Objects.nonNull(subjectInfoES)) {
                subjectInfoESList.add(subjectInfoES);
            }
        }

        /*
            设置分页返回对象
         */
        pageResult.setPageNo(req.getPageNo());
        pageResult.setPageSize(req.getPageSize());
        pageResult.setRecords(subjectInfoESList);
        pageResult.setTotal(Long.valueOf(searchHits.getTotalHits().value).intValue());
        return pageResult;
    }

    /**
     * 将SearchHit对象转换为SubjectInfoES对象
     * @param hit
     * @return
     */
    private SubjectInfoES convertResult(SearchHit hit) {
        /*
            设置SubjectInfoES中的相关属性
         */
        // 将ES查询结果中的一条数据转换为Map形式
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
        if (CollectionUtils.isEmpty(sourceAsMap)) {
            return null;
        }
        SubjectInfoES result = new SubjectInfoES();
        result.setSubjectId(MapUtils.getLong(sourceAsMap, ESSubjectFields.SUBJECT_ID));
        result.setSubjectName(MapUtils.getString(sourceAsMap, ESSubjectFields.SUBJECT_NAME));
        result.setSubjectAnswer(MapUtils.getString(sourceAsMap, ESSubjectFields.SUBJECT_ANSWER));
        result.setDocId(MapUtils.getLong(sourceAsMap, ESSubjectFields.DOC_ID));
        result.setSubjectType(MapUtils.getInteger(sourceAsMap, ESSubjectFields.SUBJECT_TYPE));
        // 处理命中文档的相关性评分：将得分乘以100.00，保留两位小数，并采用四舍五入的取整方式
        result.setScore(new BigDecimal(String.valueOf(hit.getScore())).multiply(new BigDecimal("100.00"))
                .setScale(2, RoundingMode.HALF_UP));

        /*
            处理SubjectInfoES中subject_name的高亮
         */
        // Ket -> 字段名   Value -> HighlightField对象，包含了该字段的高亮片段和其他相关信息
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField subjectNameField = highlightFields.get(ESSubjectFields.SUBJECT_NAME);
        if (Objects.nonNull(subjectNameField.getFragments())){
            Text[] fragments = subjectNameField.getFragments();
            StringBuilder subjectNameBuilder = new StringBuilder();
            for (Text fragment : fragments) {
                subjectNameBuilder.append(fragment);
            }
            result.setSubjectName(subjectNameBuilder.toString());
        }

        /*
            处理SubjectInfoES中subject_answer的高亮
         */
        HighlightField subjectAnswerField = highlightFields.get(ESSubjectFields.SUBJECT_ANSWER);
        if(Objects.nonNull(subjectAnswerField)){
            Text[] fragments = subjectAnswerField.getFragments();
            StringBuilder subjectAnswerBuilder = new StringBuilder();
            for (Text fragment : fragments) {
                subjectAnswerBuilder.append(fragment);
            }
            result.setSubjectAnswer(subjectAnswerBuilder.toString());
        }
        return result;
    }

    /**
     * 根据SubjectInfoES对象创建ESSearchRequest对象
     * @param req
     * @return
     */
    private ESSearchRequest createSearchListQuery(SubjectInfoES req) {
        ESSearchRequest esSearchRequest = new ESSearchRequest();
        /*
            设置查询条件
         */
        BoolQueryBuilder bq = new BoolQueryBuilder();
        // 关键词匹配题目名称
        MatchQueryBuilder subjectNameQueryBuilder =
                QueryBuilders.matchQuery(ESSubjectFields.SUBJECT_NAME, req.getKeyWord());
        bq.should(subjectNameQueryBuilder);
        subjectNameQueryBuilder.boost(2);   // 置提升因子为2，权重越高，在搜索结果中的排名越靠前
        // 关键词匹配题目答案
        MatchQueryBuilder subjectAnswerQueryBuilder =
                QueryBuilders.matchQuery(ESSubjectFields.SUBJECT_ANSWER, req.getKeyWord());
        bq.should(subjectAnswerQueryBuilder);
        // 设置匹配题目类别 简答题
        MatchQueryBuilder subjectTypeQueryBuilder =
                QueryBuilders.matchQuery(ESSubjectFields.SUBJECT_TYPE, SubjectInfoTypeEnum.BRIEF.getCode());
        bq.must(subjectTypeQueryBuilder);   // must表示该条件是必须满足的
        // 至少命中上述条件中的1个条件
        bq.minimumShouldMatch(1);

        /*
            设置高亮
         */
        // field("*")表示对所有字段进行高亮; requireFieldMatch(false)表示不对查询结果与字段内容是否匹配进行限制。
        HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false);
        // 设置高亮显示的前缀和后缀标签, 在文本中被包裹在这两个标签之间的内容将会以红色字体显示
        highlightBuilder.preTags("<span style = \"color:red\">");
        highlightBuilder.postTags("</span>");

        /*
            赋值
         */
        esSearchRequest.setBq(bq);
        esSearchRequest.setHighlightBuilder(highlightBuilder);
        esSearchRequest.setFields(ESSubjectFields.FIELD_QUERY);
        esSearchRequest.setFrom((req.getPageNo() - 1) * req.getPageSize());
        esSearchRequest.setSize(req.getPageSize());
        esSearchRequest.setNeedScroll(false);
        return esSearchRequest;
    }

    /**
     * 创建ESIndexInfo实例，确定集群名称和索引名称
     * @return
     */
    private ESIndexInfo getESIndexInfo(){
        ESIndexInfo esIndexInfo = new ESIndexInfo();
        esIndexInfo.setClusterName("36c268277200");
        esIndexInfo.setIndexName("subject_index");
        return esIndexInfo;
    }
}
